import Axios, {
  type AxiosInstance,
  type AxiosRequestConfig,
  type CustomParamsSerializer,
} from 'axios'
import type {
  ServerHttpError,
  RequestMethods,
  ServerHttpResponse,
  ServerHttpRequestConfig,
} from './types.d'
import { stringify } from 'qs'
import { useUserStore } from '@/stores/user'
import { isMatch } from '@/utils/detection'
import xUserService from '@/services/user'
import $config from '@/config'
import errorText from './error'

// 相关配置请参考：www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = {
  // 请求超时时间
  timeout: 10000,
  headers: {
    Accept: 'application/json, text/plain, */*',
    'Content-Type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
  },
  // 数组格式参数序列化（https://github.com/axios/axios/issues/5142）
  paramsSerializer: {
    serialize: stringify as unknown as CustomParamsSerializer,
  },
}

class ServerHttp {
  constructor() {
    this.httpInterceptorsRequest()
    this.httpInterceptorsResponse()
  }

  /** `token`过期后，暂存待执行的请求 */
  private static requests = []

  /** 防止重复刷新`token` */
  private static isRefreshing = false

  /** 初始化配置对象 */
  private static initConfig: ServerHttpRequestConfig = {}

  /** 保存当前`Axios`实例对象 */
  private static axiosInstance: AxiosInstance = Axios.create(defaultConfig)

  /** 重连原始请求 */
  private static retryOriginalRequest(config: ServerHttpRequestConfig) {
    return new Promise((resolve) => {
      ServerHttp.requests.push((token: string) => {
        config.headers['Authorization'] = 'Bearer ' + token
        resolve(config)
      })
    })
  }

  /** 请求拦截 */
  private httpInterceptorsRequest(): void {
    ServerHttp.axiosInstance.interceptors.request.use(
      async (config: ServerHttpRequestConfig): Promise<any> => {
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调
        if (typeof config.beforeRequestCallback === 'function') {
          config.beforeRequestCallback(config)
          return config
        }
        if (ServerHttp.initConfig.beforeRequestCallback) {
          ServerHttp.initConfig.beforeRequestCallback(config)
          return config
        }
        const configUrl = config.url || ''
        // 用户TOKEN
        const accessToken = xUserService.getInstance().getAccessToken()
        /** 请求白名单，放置一些不需要`token`的接口（通过设置请求白名单，防止`token`过期后再请求造成的死循环问题） */
        return isMatch($config.service.whiteList, configUrl)
          ? config
          : new Promise((resolve) => {
              if (useUserStore().isLogin) {
                // 已登录
                if (!accessToken) {
                  // 登录状态已过期
                  if (!ServerHttp.isRefreshing) {
                    ServerHttp.isRefreshing = true
                    // token过期刷新
                    useUserStore()
                      .refreshToken()
                      .then((res) => {
                        const token = res.data.accessToken
                        config.headers['Authorization'] = 'Bearer ' + token
                        ServerHttp.requests.forEach((cb) => cb(token))
                        ServerHttp.requests = []
                      })
                      .finally(() => {
                        ServerHttp.isRefreshing = false
                      })
                  }
                  resolve(ServerHttp.retryOriginalRequest(config))
                } else {
                  config.headers['Authorization'] = 'Bearer ' + accessToken
                  resolve(config)
                }
              } else {
                resolve(config)
              }
            })
      },
      (error) => {
        return Promise.reject(error)
      },
    )
  }

  /** 响应拦截 */
  private httpInterceptorsResponse(): void {
    const instance = ServerHttp.axiosInstance
    instance.interceptors.response.use(
      (response: ServerHttpResponse) => {
        const $config = response.config
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调
        if (typeof $config.beforeResponseCallback === 'function') {
          $config.beforeResponseCallback(response)
          return response as any
        }
        if (ServerHttp.initConfig.beforeResponseCallback) {
          ServerHttp.initConfig.beforeResponseCallback(response)
          return response
        }
        return response
      },
      (error: ServerHttpError) => {
        // 如果后端返回了错误消息，直接返回
        const data: any = error?.response?.data
        error.message = errorText(error.status, data?.message ?? error.message)
        const $error = error
        $error.isCancelRequest = Axios.isCancel($error)
        if (error.status === 401) {
          // 刷新失败，返回登录页
          useUserStore().logoutConfirm(error.message, true)
        } else {
          // 所有的响应异常 区分来源为取消请求/非取消请求
          return Promise.reject($error)
        }
      },
    )
  }

  /** 通用请求工具函数 */
  public request<T>(
    method: RequestMethods,
    url: string,
    param?: AxiosRequestConfig,
    axiosConfig?: ServerHttpRequestConfig,
  ): Promise<T> {
    const config = {
      method,
      url,
      ...param,
      ...axiosConfig,
    } as ServerHttpRequestConfig

    // 单独处理自定义请求/响应回调
    return new Promise((resolve, reject) => {
      config.url = $config.service.api.url(config)
      ServerHttp.axiosInstance
        .request(config)
        .then((response) => {
          const res = $config.service.api.response(response)
          resolve(res as any)
        })
        .catch((error) => {
          reject(error)
        })
    })
  }

  /** 单独抽离的`post`工具函数 */
  public post<T, P>(
    url: string,
    params?: AxiosRequestConfig<P>,
    config?: ServerHttpRequestConfig,
  ): Promise<T> {
    return this.request<T>('post', url, params, config)
  }

  /** 单独抽离的`get`工具函数 */
  public get<T, P>(
    url: string,
    params?: AxiosRequestConfig<P>,
    config?: ServerHttpRequestConfig,
  ): Promise<T> {
    return this.request<T>('get', url, params, config)
  }
}

/**
 * 手动验证原始token，并自动刷新
 *
 * @returns
 */
export const checkOriginToken = async () => {
  // 本操作会在用户发送消息前检测token是否已过期，若已过期则尝试刷新或引导重登
  // 该方法目前主要用于解决 sse 鉴权401错误的前置条件，后期可能会进行调整
  const userStore = useUserStore()
  if (!userStore.isLogin) {
    userStore.logoutConfirm('认证失败！请重新登录', true)
    return
  }
  const accessToken = xUserService.getInstance().getAccessToken()
  if (!accessToken) {
    // 尝试刷新token
    await userStore.refreshToken()
  }
}

export const http = new ServerHttp()
